﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace SearchForDestruction
{
    class Map
    {
        class Node
        {
            public int Prev { get; set; }
            public float Total { get; set; }
            public float Weight { get; set; }
            public int Type { get; set; }
        }
        
        public const int GROUND = 1 << 0;
        public const int WATER = 1 << 1;
        public const int MOUNTAIN = 1 << 2;

        int width;
        int height;
        Node[] grid;
        Queue<int> queue;
        Dictionary<int, Texture2D> textures;


        public Map(int width, int height) 
        {
            this.width = width;
            this.height = height;

            grid = new Node[width * height];
            queue = new Queue<int>();
            textures = new Dictionary<int, Texture2D>();

            for (int i = 0; i < grid.Length; ++i)
                grid[i] = new Node();
        }

        public LinkedList<int> GetPath(int startX, int startY, int endX, int endY, int types)
        {
            Clear();
            int startIndex = startY * width + startX;
            int endIndex = endY * width + endX;
            grid[startIndex].Prev = startIndex;
            grid[startIndex].Total = 0;
            queue.Enqueue(startIndex);

            while (queue.Count != 0)
            {
                int i = queue.Dequeue();

                if (i == endIndex)
                    break;

                BreatdhFirstSearch(i, types);
            }

            LinkedList<int> path = new LinkedList<int>();

            // if prev == -1 there was no path to the designated target
            if (grid[endIndex].Prev != -1)
            {
                // add the indexes from the path backwards
                path.AddFirst(endIndex);

                int prev = grid[endIndex].Prev;
                
                // fool proof test that the node has not been checked
                while (prev != -1)
                {
                    path.AddFirst(prev);

                    // if the current index is not the start index, continue looping
                    if (grid[prev].Prev != startIndex)
                        prev = grid[prev].Prev;
                    // if the current index is the start index, stop looping
                    else
                        break;
                }

                path.AddFirst(startIndex);
            }
            
            // return the path as a list of node indices.
            return path;
        }

        public void BreatdhFirstSearch(int index, int types)
        {
            int x = index % width;
            int y = index / width;
            int check = 0;

            if (x != 0)
                check |= 1;
            if (x != width - 1)
                check |= 2;
            if (y != 0)
                check |= 4;
            if (y != height - 1)
                check |= 8;
            if ((check & 5) == 5)
                check |= 16;
            if ((check & 6) == 6)
                check |= 32;
            if ((check & 9) == 9)
                check |= 64;
            if ((check & 10) == 10)
                check |= 128;

            // order of neighbours:
            //   2
            // 0 i 1
            //   3

            for (int i = 0; i < 8; ++i)
            {
                if ((check & (1 << i)) != 0)
                { 
                    int nIndex = index;
                    switch (i)
                    {
                        // straight neighbours
                        // 0 is left neighbour
                        case 0:
                            nIndex -= 1;
                            break;
                        // 1 is right neighbour
                        case 1:
                            nIndex += 1;
                            break;
                        // 2 is top neighbour
                        case 2:
                            nIndex -= width;
                            break;
                        // 3 is bottom neighbour
                        case 3:
                            nIndex += width;
                            break;
                        // diagonal neighbours
                        // top left
                        case 4:
                            nIndex -= 1 + width;
                            break;
                        // top right
                        case 5:
                            nIndex += 1 - width;
                            break;
                        // bottom left
                        case 6:
                            nIndex += width - 1;
                            break;
                        // bottom right
                        case 7:
                            nIndex += width + 1;
                            break;

                    }

                    if (grid[nIndex].Prev == -1)
                    {
                        if ((grid[nIndex].Type & types) != 0)
                        {
                            grid[nIndex].Prev = index;
                            grid[nIndex].Total = grid[nIndex].Total + grid[index].Weight;
                            queue.Enqueue(nIndex);
                        }

                        Util.Debug.Shape.DrawSolidCircleLocal((nIndex % width) * 16f + 8f, (nIndex / width) * 16f + 8f, 8, Color.Chocolate * 0.3f);
                    }
                }
            }

        }

        public void SetTile(int x, int y, int type, float weight)
        {
            int i = y * width + x;
            grid[i].Type = type;
            grid[i].Weight = weight;
        }

        public void Load(ContentManager content, string fileName)
        {
            textures.Add(GROUND, content.Load<Texture2D>("grass"));
            textures.Add(WATER, content.Load<Texture2D>("water"));
            textures.Add(MOUNTAIN, content.Load<Texture2D>("mountain"));
            
            for (int i = 0; i < grid.Length; ++i)
            {
                if (i % width == 20 && i / width > 8)
                    grid[i].Type = WATER;
                else if (i % width == 30 && i / width < 30)
                    grid[i].Type = WATER;
                else
                    grid[i].Type = GROUND;

                switch (grid[i].Type)
                {
                    case GROUND:
                        grid[i].Weight = 1f;
                        break;
                    case WATER:
                        grid[i].Weight = 3f;
                        break;
                    case MOUNTAIN:
                        grid[i].Weight = 7f;
                        break;
                }
            }
        }

        private void Clear()
        {
            queue.Clear();
            // reset Prev for all the nodes to prepare for new search.
            for(int i = 0; i < grid.Length; ++i)
                grid[i].Prev = -1;
        }
        
        public void Render(SpriteBatch batch)
        {
            for (int i = 0; i < grid.Length; ++i)
            {
                int x = i % width;
                int y = i / width;

                batch.Draw(textures[grid[i].Type], new Vector2(x * 16f, y * 16f), Color.White);
            }
        }
    }
}
